#include "wm_type_def.h"
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>

#include "wm_bt_config.h"

#if (WM_NIMBLE_INCLUDED == CFG_ON)

#include "wm_bt_app.h"
#include "wm_bt_util.h"
#include "wm_fwup.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "wm_ble_gap.h"
#include "wm_ble_server_api_demo.h"
#include "wm_ble_client_util.h"


/*
 * GLOBAL VARIABLE DEFINITIONS
 ****************************************************************************************
 */

typedef enum {
    BLE_SERVER_MODE_IDLE     = 0x00,
    BLE_SERVER_MODE_ADVERTISING = 0x01,
    BLE_SERVER_MODE_CONNECTED,
    BLE_SERVER_MODE_NOTIFING,
    BLE_SERVER_MODE_EXITING
} ble_server_state_t;
    

static int g_ota_mtu = 20;
static uint8_t g_ble_ota_notify_enable = 0;
static struct ble_gap_event_listener ble_server_event_listener;
static volatile ble_server_state_t g_ble_server_state = BLE_SERVER_MODE_IDLE;

uint16_t g_ble_character_attr_notify_handle;
uint16_t g_ble_character_attr_write_handle;
uint16_t g_ble_character_attr_read_handle;
uint16_t g_ble_ota_conn_handle ;

u32 upgrade_total = 0;
u32 upgrade_rx_len = 0;

#define WM_GATT_OTA_SVC_UUID                 0xFFFA
#define WM_GATT_OTA_READ_UUID                0xFFFB
#define WM_GATT_OTA_WRITE_UUID               0xFFFC
#define WM_GATT_OTA_NOTIFY_UUID              0xFFFD

extern const char FirmWareVer[4];


extern void change_screen_to_upgrade();
extern void set_upgrade_bar(u8 val);

static uint8_t* ota_file_cache_buffer;
static uint32_t ota_file_total_size;
static uint32_t ota_file_transfered_size;
#define OTA_FILE_WRITE_BLOCK                 2048
static uint8_t ota_content_buffer[512];
static uint32_t ota_session_id = 0;
static int
tls_ble_server_ota_gatt_access_func(uint16_t conn_handle, uint16_t attr_handle,
                              struct ble_gatt_access_ctxt *ctxt, void *arg);
static int tls_ble_server_demo_ota_set_work_mode(int work_mode);


/*
 * LOCAL FUNCTION DECLARATIONS
 ****************************************************************************************
 */


static const struct ble_gatt_svc_def gatt_ota_svr_svcs[] = {
    {
        /* Service: ota */
        .type = BLE_GATT_SVC_TYPE_PRIMARY,
        .uuid = BLE_UUID16_DECLARE(WM_GATT_OTA_SVC_UUID),
        .characteristics = (struct ble_gatt_chr_def[])
        { {
                .uuid = BLE_UUID16_DECLARE(WM_GATT_OTA_WRITE_UUID),
                .val_handle = &g_ble_character_attr_write_handle,
                .access_cb = tls_ble_server_ota_gatt_access_func,
                .flags = BLE_GATT_CHR_F_WRITE,
            }, {
                .uuid = BLE_UUID16_DECLARE(WM_GATT_OTA_READ_UUID),
                .val_handle = &g_ble_character_attr_read_handle,
                .access_cb = tls_ble_server_ota_gatt_access_func,
                .flags = BLE_GATT_CHR_F_READ,
            },{
                .uuid = BLE_UUID16_DECLARE(WM_GATT_OTA_NOTIFY_UUID),
                .val_handle = &g_ble_character_attr_notify_handle,
                .access_cb = tls_ble_server_ota_gatt_access_func,
                .flags = BLE_GATT_CHR_F_NOTIFY,
            },
			{
                0, /* No more characteristics in this service */
            }
        },
    },

    {
        0, /* No more services */
    },
};

static int tls_ble_server_ota_adv(bool enable)
{
    int rc;

    if(enable) {
        struct ble_hs_adv_fields fields;
        const char *name;
        /**
         *  Set the advertisement data included in our advertisements:
         *     o Flags (indicates advertisement type and other general info).
         *     o Device name.
         *     o user specific field (winner micro).
         */
        memset(&fields, 0, sizeof fields);
        /* Advertise two flags:
         *     o Discoverability in forthcoming advertisement (general)
         *     o BLE-only (BR/EDR unsupported).
         */
        fields.flags = BLE_HS_ADV_F_DISC_GEN |
                       BLE_HS_ADV_F_BREDR_UNSUP;
        name = ble_svc_gap_device_name();
        fields.name = (uint8_t *)name;
        fields.name_len = strlen(name);
        fields.name_is_complete = 1;
        fields.uuids16 = (ble_uuid16_t[]) {
            BLE_UUID16_INIT(WM_GATT_OTA_SVC_UUID)
        };
        fields.num_uuids16 = 1;
        fields.uuids16_is_complete = 1;
        rc = ble_gap_adv_set_fields(&fields);

        if(rc != 0) {
            TLS_BT_APPL_TRACE_ERROR("error setting advertisement data; rc=%d\r\n", rc);
            return rc;
        }

        /* As own address type we use hard-coded value, because we generate
              NRPA and by definition it's random */
        rc = tls_nimble_gap_adv(WM_BLE_ADV_IND, 0);

        if(rc != 0) {
            TLS_BT_APPL_TRACE_ERROR("tls_nimble_gap_adv; rc=%d\r\n", rc);
            return rc;
        }
    } else {
        rc = tls_nimble_gap_adv(WM_BLE_ADV_STOP, 0);
    }

    return rc;
}
/*
 * LOCAL FUNCTION DEFINITIONS
 ****************************************************************************************
 */

static void tls_print_bytes(const char *desc, uint8_t *ptr, uint16_t len)
{
    int i = 0, j = 0;
    printf("%s:", desc);
    for(i = 0; i<len; i++)
    {
        printf("%02x", ptr[i]);
        j++;

        if(j == 16){
            j = 0;
            printf("\r\n");
        }
    }
    printf("\r\n");
}

static int
tls_ble_server_ota_gatt_access_func(uint16_t conn_handle, uint16_t attr_handle,
                              struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    int rc;
	int total_len = 0;
    struct os_mbuf *om = ctxt->om;

    switch(ctxt->op) {
        case BLE_GATT_ACCESS_OP_WRITE_CHR:
            while(om) {
                //tls_print_bytes("op write", om->om_data, om->om_len);
                memcpy(ota_content_buffer+total_len, om->om_data, om->om_len);
                total_len += om->om_len;
                om = SLIST_NEXT(om, om_next);
            }
			TLS_BT_APPL_TRACE_VERBOSE("got %s len=%d\r\n", __FUNCTION__, total_len);

            upgrade_rx_len+=total_len;

            if(upgrade_total !=0){
                u8 val = upgrade_rx_len*100/upgrade_total;
                // printf("---> val : %d\n", val);
                set_upgrade_bar(val);
            }

            rc = tls_fwup_request_sync(ota_session_id, ota_content_buffer+3, total_len-3);
            if(rc != TLS_FWUP_STATUS_OK)
            {
                TLS_BT_APPL_TRACE_ERROR("tls_fwup_request_sync, failed rc=%d", rc);
            }
            if(ota_content_buffer[2] == 1)
            {
                rc = tls_fwup_current_state(ota_session_id);
                if(rc == TLS_FWUP_STATE_COMPLETE)
                {
                    TLS_BT_APPL_TRACE_DEBUG("fwup finished...")
                }
                tls_fwup_exit(ota_session_id);
                TLS_BT_APPL_TRACE_DEBUG("fwup exited...")
            }
            return 0;
        case BLE_GATT_ACCESS_OP_READ_CHR:
            rc = os_mbuf_append(ctxt->om, FirmWareVer, 4);
            return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;

        default:
            assert(0);
            return BLE_ATT_ERR_UNLIKELY;
    }
}

static int tls_ble_server_ota_gatt_svr_init(void)
{
    int rc;
    rc = ble_gatts_count_cfg(gatt_ota_svr_svcs);

    if(rc != 0) {
        goto err;
    }

    rc = ble_gatts_add_svcs(gatt_ota_svr_svcs);

    if(rc != 0) {
        return rc;
    }

err:
    return rc;
}


static void tls_ble_server_ota_conn_param_update_cb(uint16_t conn_handle, int status, void *arg)
{
    TLS_BT_APPL_TRACE_DEBUG("conn param update complete; conn_handle=%d status=%d\n",
                            conn_handle, status);
    if(status!=0)
    {
        
    }
}

static int tls_ble_server_ota_on_mtu(uint16_t conn_handle, const struct ble_gatt_error *error,
                          uint16_t mtu, void *arg)
{
    switch(error->status) {
        case 0:
            TLS_BT_APPL_TRACE_DEBUG("mtu exchange complete: conn_handle=%d mtu=%d\r\n",
                                    conn_handle, mtu);
            g_ota_mtu = mtu-3;
            break;

        default:
            TLS_BT_APPL_TRACE_DEBUG("Update MTU failed...error->status=%d\r\n", error->status);
            break;
    }

    return 0;
}

static void tls_ble_server_ota_mtu_update()
{
    int rc;
    
    rc = ble_gattc_exchange_mtu(g_ble_ota_conn_handle, tls_ble_server_ota_on_mtu, NULL);
    if(rc!= 0 )
    {
        TLS_BT_APPL_TRACE_WARNING("WARN, ble_gattc_exchange_mtu rc=%d\r\n", rc);
    }
}

static void conn_param_update_cb(uint16_t conn_handle, int status, void *arg)
{
    TLS_BT_APPL_TRACE_DEBUG("conn param update complete; conn_handle=%d status=%d\n",
                            conn_handle, status);
    if(status!=0)
    {
        
    }
}

static void tls_ble_server_conn_param_update_slave()
{
    int rc;
    struct ble_l2cap_sig_update_params params;
    params.itvl_min = 10;
    params.itvl_max = 12;
    params.slave_latency = 0;
    params.timeout_multiplier = 500;
    rc = ble_l2cap_sig_update(g_ble_ota_conn_handle, &params,
                              conn_param_update_cb, NULL);
    if(rc != 0)
    {
        TLS_BT_APPL_TRACE_ERROR("ERROR, ble_l2cap_sig_update rc=%d\r\n", rc);
    }
}

static int tls_ble_server_ota_gap_evt_cb(struct ble_gap_event *event, void *arg)
{
    int rc;
    struct ble_gap_conn_desc desc;

    switch(event->type) {
        case BLE_GAP_EVENT_CONNECT:
            
            if(event->connect.status == 0) {

                rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
                if(rc != 0) return 0;
                if(desc.role != BLE_GAP_ROLE_SLAVE){
                    return 0;
                }
                TLS_BT_APPL_TRACE_DEBUG("Server connected status=%d handle=%d\r\n",
                                    event->connect.status, g_ble_ota_conn_handle);                
                print_conn_desc(&desc);
                g_ble_server_state = BLE_SERVER_MODE_CONNECTED;
                //re set this flag, to prevent stop adv, but connected evt reported when deinit this demo
                g_ble_ota_conn_handle = event->connect.conn_handle;
                tls_bt_async_proc_func(tls_ble_server_conn_param_update_slave, NULL, 100);
                tls_bt_async_proc_func(tls_ble_server_ota_mtu_update, NULL, 200);
            }

            if(event->connect.status != 0) {
                /* Connection failed; resume advertising. */
                tls_nimble_gap_adv(WM_BLE_ADV_IND, 0);
            }

            break;

        case BLE_GAP_EVENT_DISCONNECT:

            if(event->disconnect.conn.role != BLE_GAP_ROLE_SLAVE) return 0;

            TLS_BT_APPL_TRACE_DEBUG("Server disconnect reason=%d[0x%02x],state=%d\r\n", event->disconnect.reason,event->disconnect.reason-0x200,
                                    g_ble_server_state);
            g_ble_ota_notify_enable = 0;
            g_ota_mtu = 20;
            tls_fwup_exit(ota_session_id);

            if(g_ble_server_state == BLE_SERVER_MODE_EXITING) {
                g_ble_server_state = BLE_SERVER_MODE_IDLE;
                ble_gap_event_listener_unregister(&ble_server_event_listener);
            } else {

                /**if connction is not closed by local host*/
                /**Any reason, we will continue to do advertise*/
                // 1, indicate timeout, host will terminate the connection, disconnect reason 0x16
                // 2, host stack close, negative effection, no!
                if(event->disconnect.reason != 534)
                {
                    rc = tls_nimble_gap_adv(WM_BLE_ADV_IND, 0);
                    TLS_BT_APPL_TRACE_DEBUG("Disconnect evt, and continue to do adv, rc=%d\r\n", rc);

                    if(!rc) {
                        g_ble_server_state = BLE_SERVER_MODE_ADVERTISING;
                    } else {
                        g_ble_server_state = BLE_SERVER_MODE_IDLE;
                    }
                }else
                {
                    g_ble_server_state = BLE_SERVER_MODE_IDLE;
                }
            }

            break;

        case BLE_GAP_EVENT_NOTIFY_TX:

            rc = ble_gap_conn_find(event->notify_tx.conn_handle , &desc);
            if(rc != 0){
                break;
            }
            if(desc.role != BLE_GAP_ROLE_SLAVE) return 0;


            break;

        case BLE_GAP_EVENT_SUBSCRIBE:
            rc = ble_gap_conn_find(event->subscribe.conn_handle , &desc);
            if(rc != 0) return 0;
            if(desc.role != BLE_GAP_ROLE_SLAVE) return 0;
            
            if(event->subscribe.cur_notify)
            {
                ota_session_id = tls_fwup_enter(0);
                tls_ble_server_demo_ota_set_work_mode(1);
            }else
            {
                tls_fwup_exit(ota_session_id);
            }
            TLS_BT_APPL_TRACE_DEBUG("subscribe notify %s ota start session id is %d\r\n",event->subscribe.cur_notify?"enable":"disable", ota_session_id);

            break;

        case BLE_GAP_EVENT_MTU:
            rc = ble_gap_conn_find(event->mtu.conn_handle , &desc);
            if(rc != 0) return 0;
            if(desc.role != BLE_GAP_ROLE_SLAVE) return 0;

            TLS_BT_APPL_TRACE_DEBUG("ota service mtu changed to %d\r\n", event->mtu.value);
            /*nimBLE config prefered ATT_MTU is 256. here 256-12 = 244. */
            /* preamble(1)+access address(4)+pdu(2~257)+crc*/
            /* ATT_MTU(247):pdu= pdu_header(2)+l2cap_len(2)+l2cap_chn(2)+mic(4)*/
            /* GATT MTU(244): ATT_MTU +opcode+chn*/
            //g_mtu = min(event->mtu.value - 12, 244);
            g_ota_mtu = event->mtu.value-3;
            break;

        case BLE_GAP_EVENT_REPEAT_PAIRING:
            /* We already have a bond with the peer, but it is attempting to
             * establish a new secure link.  This app sacrifices security for
             * convenience: just throw away the old bond and accept the new link.
             */
            /* Delete the old bond. */
            rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
            if(rc != 0) return 0;
            if(desc.role != BLE_GAP_ROLE_SLAVE) return 0;            
            ble_store_util_delete_peer(&desc.peer_id_addr);
            TLS_BT_APPL_TRACE_DEBUG("!!!BLE_GAP_EVENT_REPEAT_PAIRING\r\n");
            return BLE_GAP_REPEAT_PAIRING_RETRY;

        case BLE_GAP_EVENT_PASSKEY_ACTION:
            TLS_BT_APPL_TRACE_DEBUG(">>>BLE_GAP_EVENT_REPEAT_PAIRING\r\n");
            return 0;
        case BLE_GAP_EVENT_L2CAP_UPDATE_REQ:
        case BLE_GAP_EVENT_CONN_UPDATE_REQ:
            TLS_BT_APPL_TRACE_DEBUG("Server conn handle=%d,peer:[min=%d,max=%d,sup=%d],local[min=%d,max=%d,sup=%d]", event->conn_update_req.conn_handle, 
                event->conn_update_req.peer_params->itvl_min, event->conn_update_req.peer_params->itvl_max,event->conn_update_req.peer_params->supervision_timeout,
                event->conn_update_req.self_params->itvl_min, event->conn_update_req.self_params->itvl_max,event->conn_update_req.self_params->supervision_timeout);
            break;
        case BLE_GAP_EVENT_CONN_UPDATE:
            if(event->conn_update.status == 0)
            {
                rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
                assert(rc == 0);
                TLS_BT_APPL_TRACE_DEBUG("Server con_handle=%d,conn update, interval=%d, supervision=%d\r\n", event->conn_update.conn_handle, desc.conn_itvl, desc.supervision_timeout);
                
                change_screen_to_upgrade();
                upgrade_total = 0;
                upgrade_rx_len = 0;
            }
            break;
        case BLE_GAP_EVENT_HOST_SHUTDOWN:
            TLS_BT_APPL_TRACE_DEBUG("Server BLE_GAP_EVENT_HOST_SHUTDOWN:%d\r\n", event->type);
            g_ble_server_state = BLE_SERVER_MODE_IDLE;
            g_ota_mtu = 20;
             
            break;
        default:
            TLS_BT_APPL_TRACE_VERBOSE("Server Unhandled event:%d\r\n", event->type);
  
            break;
    }

    return 0;
}


/*
 * EXPORTED FUNCTION DEFINITIONS
 ****************************************************************************************
 */

int tls_ble_server_demo_ota_init()
{
    int rc = BLE_HS_EAPP;

    CHECK_SYSTEM_READY();

    TLS_BT_APPL_TRACE_DEBUG("%s, state=%d\r\n", __FUNCTION__, g_ble_server_state);

    if(g_ble_server_state == BLE_SERVER_MODE_IDLE) {
        //step 0: reset other services. Note
        rc = ble_gatts_reset();

        if(rc != 0) {
            TLS_BT_APPL_TRACE_ERROR("tls_ble_server_demo_ota_init failed rc=%d\r\n", rc);
            return rc;
        }

        //step 1: config/adding  the services
        rc = tls_ble_server_ota_gatt_svr_init();

        if(rc == 0) {
            ble_gap_event_listener_register(&ble_server_event_listener,
                                            tls_ble_server_ota_gap_evt_cb, NULL);

            TLS_BT_APPL_TRACE_DEBUG("tls_ble_server_demo_ota_init register success\r\n");

            /*step 2: start the service*/
            rc = ble_gatts_start();
            assert(rc == 0);
            /*step 3: start advertisement*/
            rc = tls_ble_server_ota_adv(true);

            if(rc == 0) {
                g_ble_server_state = BLE_SERVER_MODE_ADVERTISING;
            }
        } else {
            TLS_BT_APPL_TRACE_ERROR("tls_ble_server_demo_ota_init failed(rc=%d)\r\n", rc);
        }
    } else {
        TLS_BT_APPL_TRACE_WARNING("tls_ble_server_demo_ota_init registered\r\n");
        rc = BLE_HS_EALREADY;
    }

    return rc;
}
int tls_ble_server_demo_ota_deinit()
{
    int rc = BLE_HS_EAPP;

    CHECK_SYSTEM_READY();

    TLS_BT_APPL_TRACE_DEBUG("%s, state=%d\r\n", __FUNCTION__, g_ble_server_state);

    if(g_ble_server_state == BLE_SERVER_MODE_CONNECTED
            || g_ble_server_state == BLE_SERVER_MODE_NOTIFING) {
        rc = ble_gap_terminate(g_ble_ota_conn_handle, BLE_ERR_REM_USER_CONN_TERM);

        if(rc == 0) {
            g_ble_server_state = BLE_SERVER_MODE_EXITING;
        } else if(rc == BLE_HS_EDISABLED || rc == BLE_HS_ENOTCONN) {
            g_ble_server_state = BLE_SERVER_MODE_IDLE;
            ble_gap_event_listener_unregister(&ble_server_event_listener);
        }else{
            g_ble_server_state = BLE_SERVER_MODE_IDLE;  
            ble_gap_event_listener_unregister(&ble_server_event_listener);
        }
    } else if(g_ble_server_state == BLE_SERVER_MODE_ADVERTISING) {
        rc = tls_nimble_gap_adv(WM_BLE_ADV_STOP, 0);

        if(rc == 0 || rc == BLE_HS_EDISABLED || rc == BLE_HS_EALREADY) {
            g_ble_server_state = BLE_SERVER_MODE_IDLE;
            ble_gap_event_listener_unregister(&ble_server_event_listener);
        }
    } else if(g_ble_server_state == BLE_SERVER_MODE_IDLE) {
        rc = 0;
    } else {
        rc = BLE_HS_EALREADY;
    }

    return rc;
}
uint32_t tls_ble_server_demo_ota_get_mtu()
{
    return g_ota_mtu;
}



int tls_ble_server_demo_ota_send_msg(uint8_t *ptr, int length)
{
    int rc;
    struct os_mbuf *om;
    
    CHECK_SYSTEM_READY();

    TLS_BT_APPL_TRACE_VERBOSE("### %s len=%d\r\n", __FUNCTION__, g_ota_mtu);

    om = ble_hs_mbuf_from_flat(ptr, length);

    if(!om) {
        return BLE_HS_ENOMEM;
    }

    rc = ble_gattc_notify_custom(g_ble_ota_conn_handle, g_ble_character_attr_notify_handle, om);

    if(rc != 0) 
    {
        TLS_BT_APPL_TRACE_DEBUG("tls_ble_server_demo_ota_send_msg , rc=%d\r\n", rc)
    }

    return rc;
}

static int tls_ble_server_demo_ota_set_work_mode(int work_mode)
{
    int rc;
    int intv_min = 0x06;
    int intv_max = 0x06;
    struct ble_l2cap_sig_update_params params;
    
#define PIC_TRANSFERING 1
#define CONNECTING_KEEP 0
#define CONNECTING_KEEP_AND_LOW_POWER 2

    switch(work_mode)
    {
        case 0: 
            intv_min = 100;
            intv_max = 120;
            break;
        case 1:
            intv_min = 10;
            intv_max = 12;
            break;      
        case 2:
            intv_min = 200;
            intv_max = 240; 
            break;
        default:
            TLS_BT_APPL_TRACE_DEBUG("Unspported work mode %d\r\n", work_mode);
            return -1;
    }
    
    params.itvl_min = intv_min;
    params.itvl_max = intv_max;
    params.slave_latency = 0;
    params.timeout_multiplier = 500;
    rc = ble_l2cap_sig_update(g_ble_ota_conn_handle, &params,
                              tls_ble_server_ota_conn_param_update_cb, NULL);
    if(rc != 0)
    {
        TLS_BT_APPL_TRACE_ERROR("ERROR, ble_l2cap_sig_update rc=%d\r\n", rc);
    } 

    return rc;
}

#endif



